1# Paths, Polygons and Regions Tutorial
  2
  3<!-- TOC -->
  4
  5## Paths
  6A number of advanced features in BOSL2 rely on paths, which are just ordered lists of points.
  7
  8First-off, some terminology:
  9- A 2D point is a vector of X and Y axis position values.  ie: `[3,4]` or `[7,-3]`.
 10- A 3D point is a vector of X, Y and Z axis position values.  ie: `[3,4,2]` or `[-7,5,3]`.
 11- A 2D path is simply a list of two or more 2D points.  ie: `[[5,7], [1,-5], [-5,6]]`
 12- A 3D path is simply a list of two or more 3D points.  ie: `[[5,7,-1], [1,-5,3], [-5,6,1]]`
 13- A polygon is a 2D (or planar 3D) path where the last point is assumed to connect to the first point.
 14- A region is a list of 2D polygons, where each polygon is XORed against all the others.  ie: if one polygon is inside another, it makes a hole in the first polygon.
 15
 16### Stroke
 17A path can be hard to visualize, since it's just a bunch of numbers in the source code.
 18One way to see the path is to pass it to `polygon()`:
 19
 20```openscad-2D
 21include <BOSL2/std.scad>
 22path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
 23polygon(path);
 24```
 25
 26Sometimes, however, it's easier to see just the path itself.  For this, you can use the `stroke()` module.
 27At its most basic, `stroke()` just shows the path's line segments:
 28
 29```openscad-2D
 30include <BOSL2/std.scad>
 31path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
 32stroke(path);
 33```
 34
 35You can vary the width of the drawn path with the `width=` argument:
 36
 37```openscad-2D
 38include <BOSL2/std.scad>
 39path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
 40stroke(path, width=3);
 41```
 42
 43You can vary the line length along the path by giving a list of widths, one per point:
 44
 45```openscad-2D
 46include <BOSL2/std.scad>
 47path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
 48stroke(path, width=[3,2,1,2,3]);
 49```
 50
 51If a path is meant to represent a closed polygon, you can use `closed=true` to show it that way:
 52
 53```openscad-2D
 54include <BOSL2/std.scad>
 55path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
 56stroke(path, closed=true);
 57```
 58
 59The ends of the drawn path are normally capped with a "round" endcap, but there are other options:
 60
 61```openscad-2D
 62include <BOSL2/std.scad>
 63path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
 64stroke(path, endcaps="round");
 65```
 66
 67```openscad-2D
 68include <BOSL2/std.scad>
 69path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
 70stroke(path, endcaps="butt");
 71```
 72
 73```openscad-2D
 74include <BOSL2/std.scad>
 75path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
 76stroke(path, endcaps="line");
 77```
 78
 79```openscad-2D
 80include <BOSL2/std.scad>
 81path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
 82stroke(path, endcaps="tail");
 83```
 84
 85```openscad-2D
 86include <BOSL2/std.scad>
 87path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
 88stroke(path, endcaps="arrow2");
 89```
 90
 91For more standard supported endcap options, see the docs for [`stroke()`](shapes2d.scad#stroke).
 92
 93The start and ending endcaps can be specified individually or separately, using `endcap1=` and `endcap2=`:
 94
 95```openscad-2D
 96include <BOSL2/std.scad>
 97path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
 98stroke(path, endcap2="arrow2");
 99```
100
101```openscad-2D
102include <BOSL2/std.scad>
103path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
104stroke(path, endcap1="butt", endcap2="arrow2");
105```
106
107```openscad-2D
108include <BOSL2/std.scad>
109path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
110stroke(path, endcap1="tail", endcap2="arrow");
111```
112
113The size of the endcaps will be relative to the width of the line where the endcap is to be placed:
114
115```openscad-2D
116include <BOSL2/std.scad>
117path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
118widths = [1, 1.25, 1.5, 1.75, 2];
119stroke(path, width=widths, endcaps="arrow2");
120```
121
122If none of the standard endcaps are useful to you, it is possible to design your own, simply by
123passing a path to the `endcaps=`, `endcap1=`, or `endcap2=` arguments.  You may also need to give
124`trim=` to tell it how far back to trim the main line, so it renders nicely.  The values in the
125endcap polygon, and in the `trim=` argument are relative to the line width.  A value of 1 is one
126line width size.
127
128Untrimmed:
129
130```openscad-2D
131include <BOSL2/std.scad>
132path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
133dblarrow = [[0,0], [2,-3], [0.5,-2.3], [2,-4], [0.5,-3.5], [-0.5,-3.5], [-2,-4], [-0.5,-2.3], [-2,-3]];
134stroke(path, endcaps=dblarrow);
135```
136
137Trimmed:
138
139```openscad-2D
140include <BOSL2/std.scad>
141path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
142dblarrow = [[0,0], [2,-3], [0.5,-2.3], [2,-4], [0.5,-3.5], [-0.5,-3.5], [-2,-4], [-0.5,-2.3], [-2,-3]];
143stroke(path, trim=3.5, endcaps=dblarrow);
144```
145
146### Standard 2D Shape Polygons
147BOSL2 will let you get the perimeter polygon for almost all of the standard 2D shapes simply by calling them like a function:
148
149```openscad-2D
150include <BOSL2/std.scad>
151path = square(40, center=true);
152stroke(list_wrap(path), endcap2="arrow2");
153```
154
155```openscad-2D
156include <BOSL2/std.scad>
157path = rect([40,30], rounding=5);
158stroke(list_wrap(path), endcap2="arrow2");
159```
160
161```openscad-2D
162include <BOSL2/std.scad>
163path = trapezoid(w1=40, w2=20, h=30);
164stroke(list_wrap(path), endcap2="arrow2");
165```
166
167```openscad-2D
168include <BOSL2/std.scad>
169path = circle(d=50);
170stroke(list_wrap(path), endcap2="arrow2");
171```
172
173```openscad-2D
174include <BOSL2/std.scad>
175path = ellipse(d=[50,30]);
176stroke(list_wrap(path), endcap2="arrow2");
177```
178
179```openscad-2D
180include <BOSL2/std.scad>
181path = pentagon(d=50);
182stroke(list_wrap(path), endcap2="arrow2");
183```
184
185```openscad-2D
186include <BOSL2/std.scad>
187path = star(n=5, step=2, d=50);
188stroke(list_wrap(path), endcap2="arrow2");
189```
190
191### Arcs
192Often, when you are constructing a path, you will want to add an arc.  The `arc()` command lets you do that:
193
194```openscad-2D
195include <BOSL2/std.scad>
196path = arc(r=30, angle=120);
197stroke(path, endcap2="arrow2");
198```
199
200```openscad-2D
201include <BOSL2/std.scad>
202path = arc(d=60, angle=120);
203stroke(path, endcap2="arrow2");
204```
205
206If you give the `n=` argument, you can control exactly how many points the arc is divided into:
207
208```openscad-2D
209include <BOSL2/std.scad>
210path = arc(n=5, r=30, angle=120);
211stroke(path, endcap2="arrow2");
212```
213
214With the `start=` argument, you can start the arc somewhere other than the X+ axis:
215
216```openscad-2D
217include <BOSL2/std.scad>
218path = arc(start=45, r=30, angle=120);
219stroke(path, endcap2="arrow2");
220```
221
222Alternatively, you can give starting and ending angles in a list in the `angle=` argument:
223
224```openscad-2D
225include <BOSL2/std.scad>
226path = arc(angle=[120,45], r=30);
227stroke(path, endcap2="arrow2");
228```
229
230The `cp=` argument lets you center the arc somewhere other than the origin:
231
232```openscad-2D
233include <BOSL2/std.scad>
234path = arc(cp=[10,0], r=30, angle=120);
235stroke(path, endcap2="arrow2");
236```
237
238The arc can also be given by three points on the arc:
239
240```openscad-2D
241include <BOSL2/std.scad>
242pts = [[-15,10],[0,20],[35,-5]];
243path = arc(points=pts);
244stroke(path, endcap2="arrow2");
245```
246
247
248### Turtle Graphics
249Another way you can create a path is using the `turtle()` command.  It implements a simple path
250description language that is similar to LOGO Turtle Graphics. The concept is that you have a virtial
251turtle or cursor walking a path.  It can "move" forward or backward, or turn "left" or "right" in
252place:
253
254```openscad-2D
255include <BOSL2/std.scad>
256path = turtle([
257    "move", 10,
258    "left", 90,
259    "move", 20,
260    "left", 135,
261    "move", 10*sqrt(2),
262    "right", 90,
263    "move", 10*sqrt(2),
264    "left", 135,
265    "move", 20
266]);
267stroke(path, endcap2="arrow2");
268```
269
270The position and the facing of the turtle/cursor updates after each command.  The motion and turning
271commands can also have default distances or angles given:
272
273```openscad-2D
274include <BOSL2/std.scad>
275path = turtle([
276    "angle",360/6,
277    "length",10,
278    "move","turn",
279    "move","turn",
280    "move","turn",
281    "move","turn",
282    "move"
283]);
284stroke(path, endcap2="arrow2");
285```
286
287You can use "scale" to relatively scale up the default motion length:
288
289```openscad-2D
290include <BOSL2/std.scad>
291path = turtle([
292    "angle",360/6,
293    "length",10,
294    "move","turn",
295    "move","turn",
296    "scale",2,
297    "move","turn",
298    "move","turn",
299    "scale",0.5,
300    "move"
301]);
302stroke(path, endcap2="arrow2");
303```
304
305Sequences of commands can be repeated using the "repeat" command:
306
307```openscad-2D
308include <BOSL2/std.scad>
309path=turtle([
310    "angle",360/5,
311    "length",10,
312    "repeat",5,["move","turn"]
313]);
314stroke(path, endcap2="arrow2");
315```
316
317More complicated commands also exist, including those that form arcs:
318
319```openscad-2D
320include <BOSL2/std.scad>
321path = turtle([
322    "move", 10,
323    "left", 90,
324    "move", 20,
325    "arcleft", 10, 180,
326    "move", 20
327]);
328stroke(path, endcap2="arrow2");
329```
330
331A comprehensive list of supported turtle commands can be found in the docs for [`turtle()`](shapes2d.scad#turtle).
332
333### Transforming Paths and Polygons
334To translate a path, you can just pass it to the `move()` (or up/down/left/right/fwd/back) function in the `p=` argument:
335
336```openscad-2D
337include <BOSL2/std.scad>
338path = move([-15,-30], p=square(50,center=true));
339stroke(list_wrap(path), endcap2="arrow2");
340```
341
342```openscad-2D
343include <BOSL2/std.scad>
344path = fwd(30, p=square(50,center=true));
345stroke(list_wrap(path), endcap2="arrow2");
346```
347
348```openscad-2D
349include <BOSL2/std.scad>
350path = left(30, p=square(50,center=true));
351stroke(list_wrap(path), endcap2="arrow2");
352```
353
354To scale a path, you can just pass it to the `scale()` (or [xyz]scale) function in the `p=` argument:
355
356```openscad-2D
357include <BOSL2/std.scad>
358path = scale([1.5,0.75], p=square(50,center=true));
359stroke(list_wrap(path), endcap2="arrow2");
360```
361
362```openscad-2D
363include <BOSL2/std.scad>
364path = xscale(1.5, p=square(50,center=true));
365stroke(list_wrap(path), endcap2="arrow2");
366```
367
368```openscad-2D
369include <BOSL2/std.scad>
370path = yscale(1.5, p=square(50,center=true));
371stroke(list_wrap(path), endcap2="arrow2");
372```
373
374To rotate a path, just can pass it to the `rot()` (or [xyz]rot) function in the `p=` argument:
375
376```openscad-2D
377include <BOSL2/std.scad>
378path = rot(30, p=square(50,center=true));
379stroke(list_wrap(path), endcap2="arrow2");
380```
381
382```openscad-2D
383include <BOSL2/std.scad>
384path = zrot(30, p=square(50,center=true));
385stroke(list_wrap(path), endcap2="arrow2");
386```
387
388To mirror a path, just can pass it to the `mirror()` (or [xyz]flip) function in the `p=` argument:
389
390```openscad-2D
391include <BOSL2/std.scad>
392path = mirror([1,1], p=trapezoid(w1=40, w2=10, h=25));
393stroke(list_wrap(path), endcap2="arrow2");
394```
395
396```openscad-2D
397include <BOSL2/std.scad>
398path = xflip(p=trapezoid(w1=40, w2=10, h=25));
399stroke(list_wrap(path), endcap2="arrow2");
400```
401
402```openscad-2D
403include <BOSL2/std.scad>
404path = yflip(p=trapezoid(w1=40, w2=10, h=25));
405stroke(list_wrap(path), endcap2="arrow2");
406```
407
408You can get raw transformation matrices for various transformations by calling them like a function without a `p=` argument:
409
410```openscad-2D
411include <BOSL2/std.scad>
412mat = move([5,10,0]);
413multmatrix(mat) square(50,center=true);
414```
415
416```openscad-2D
417include <BOSL2/std.scad>
418mat = scale([1.5,0.75,1]);
419multmatrix(mat) square(50,center=true);
420```
421
422```openscad-2D
423include <BOSL2/std.scad>
424mat = rot(30);
425multmatrix(mat) square(50,center=true);
426```
427
428Raw transformation matrices can be multiplied together to precalculate a compound transformation.  For example, to scale a shape, then rotate it, then translate the result, you can do something like:
429
430```openscad-2D
431include <BOSL2/std.scad>
432mat = move([5,10,0]) * rot(30) * scale([1.5,0.75,1]);
433multmatrix(mat) square(50,center=true);
434```
435
436To apply a compound transformation matrix to a path, you can use the `apply()` function:
437
438```openscad-2D
439include <BOSL2/std.scad>
440mat = move([5,10]) * rot(30) * scale([1.5,0.75]);
441path = square(50,center=true);
442tpath = apply(mat, path);
443stroke(tpath, endcap2="arrow2");
444```
445
446
447### Regions
448A polygon is good to denote a single closed 2D shape with no holes in it.  For more complex 2D
449shapes, you will need to use regions.  A region is a list of 2D polygons, where each polygon is
450XORed against all the others.  You can display a region using the `region()` module.
451
452If you have a region with one polygon fully inside another, it makes a hole:
453
454```openscad-2D
455include <BOSL2/std.scad>
456rgn = [square(50,center=true), circle(d=30)];
457region(rgn);
458```
459
460If you have a region with multiple polygons that are not contained by any others, they make multiple discontiguous shapes:
461
462```openscad-2D
463include <BOSL2/std.scad>
464rgn = [
465    move([-30, 20], p=square(20,center=true)),
466    move([  0,-20], p=trapezoid(w1=20, w2=10, h=20)),
467    move([ 30, 20], p=square(20,center=true)),
468];
469region(rgn);
470```
471
472Region polygons can be nested abitrarily deep, in multiple discontiguous shapes:
473
474```openscad-2D
475include <BOSL2/std.scad>
476rgn = [
477    for (d=[50:-10:10]) left(30, p=circle(d=d)),
478    for (d=[50:-10:10]) right(30, p=circle(d=d))
479];
480region(rgn);
481```
482
483A region with crossing polygons is somewhat poorly formed, but the intersection(s) of the polygons become holes:
484
485```openscad-2D
486include <BOSL2/std.scad>
487rgn = [
488    left(15, p=circle(d=50)),
489    right(15, p=circle(d=50))
490];
491region(rgn);
492```
493
494### Boolean Region Geometry
495Similarly to how OpenSCAD can perform operations like union/difference/intersection/offset on shape geometry,
496the BOSL2 library lets you perform those same operations on regions:
497
498```openscad-2D
499include <BOSL2/std.scad>
500rgn1 = [for (d=[40:-10:10]) circle(d=d)];
501rgn2 = [square([60,12], center=true)];
502rgn = union(rgn1, rgn2);
503region(rgn);
504```
505
506```openscad-2D
507include <BOSL2/std.scad>
508rgn1 = [for (d=[40:-10:10]) circle(d=d)];
509rgn2 = [square([60,12], center=true)];
510rgn = difference(rgn1, rgn2);
511region(rgn);
512```
513
514```openscad-2D
515include <BOSL2/std.scad>
516rgn1 = [for (d=[40:-10:10]) circle(d=d)];
517rgn2 = [square([60,12], center=true)];
518rgn = intersection(rgn1, rgn2);
519region(rgn);
520```
521
522```openscad-2D
523include <BOSL2/std.scad>
524rgn1 = [for (d=[40:-10:10]) circle(d=d)];
525rgn2 = [square([60,12], center=true)];
526rgn = exclusive_or(rgn1, rgn2);
527region(rgn);
528```
529
530```openscad-2D
531include <BOSL2/std.scad>
532orig_rgn = [star(n=5, step=2, d=50)];
533rgn = offset(orig_rgn, r=-3, closed=true);
534color("blue") region(orig_rgn);
535region(rgn);
536```
537
538You can use regions for several useful things.  If you wanted a grid of holes in your object that
539form the shape given by a region, you can do that with `grid_copies()`:
540
541```openscad-3D
542include <BOSL2/std.scad>
543rgn = [
544    circle(d=100),
545    star(n=5,step=2,d=100,spin=90)
546];
547difference() {
548    cyl(h=5, d=120);
549    grid_copies(size=[120,120], spacing=[4,4], inside=rgn) cyl(h=10,d=2);
550}
551```
552
553You can also sweep a region through 3-space to make a solid:
554
555```openscad-3D
556include <BOSL2/std.scad>
557$fa=1; $fs=1;
558rgn = [ for (d=[50:-10:10]) circle(d=d) ];
559tforms = [
560    for (a=[90:-5:0]) xrot(a, cp=[0,-70]),
561    for (a=[0:5:90]) xrot(a, cp=[0,70]),
562    move([0,150,-70]) * xrot(90),
563];
564sweep(rgn, tforms, closed=false, caps=true);
565```
566
567
568